using Lichtspiel.Display;

namespace Lichtspiel.Math
{
    public class InterpolationHelper
    {
        private const float DefaultEaseElasticPeriod = 0.7f;
        private const float DefaultEaseRate = 2f;
        private const double EaseBackOvershoot = 1.70158d;

        #region ------------------------------ Linear ------------------------------

        public static double Linear(double start, double end, float amount) {
            return start + (end - start) * amount;
        }

        public static float Linear(float start, float end, float amount) {
            return start + (end - start) * amount;
        }

        public static Color Linear(Color start, Color end, float amount) {
            var r = Linear(start.R, end.R, amount);
            var g = Linear(start.G, end.G, amount);
            var b = Linear(start.B, end.B, amount);
            var a = Linear(start.A, end.A, amount);

            return new Color(r,g,b,a);
        }

        public static Vector2 Linear(Vector2 start, Vector2 end, float amount) {
            float x = Linear(start.X, end.X, amount);
            float y = Linear(start.Y, end.Y, amount);

            return new Vector2(x, y);
        }

        #endregion

        #region ------------------------------ Hermite ------------------------------

        public static double Hermite(double start, double startTangent, double end, double endTangent, float amount) {
            double v1 = start, v2 = end, t1 = startTangent, t2 = endTangent, s = amount, result;
            double sCubed = s * s * s;
            double sSquared = s * s;

            if (amount == 0f) {
                result = start;
            } else if (amount == 1f) {
                result = end;
            } else {
                result = (2 * v1 - 2 * v2 + t2 + t1) * sCubed + (3 * v2 - 3 * v1 - 2 * t1 - t2) * sSquared + t1 * s + v1;
            }
            return result;
        }

        public static float Hermite(float start, float startTangent, float end, float endTangent, float amount) {
            return (float)Hermite((double)start, (double)startTangent, (double)end, (double)endTangent, amount);
        }

        public static Vector2 Hermite(Vector2 start, Vector2 startTangent, Vector2 end, Vector2 endTangent,
                                      float amount) {
            float x = Hermite(start.X, startTangent.X, end.X, endTangent.X, amount);
            float y = Hermite(start.Y, startTangent.Y, end.Y, endTangent.Y, amount);

            return new Vector2(x, y);
        }

        #endregion

        #region ------------------------------ EaseInOut ------------------------------
        /* old
        public static double EaseInOut(double start, double end, float amount) {
            return Hermite(start, 0f, end, 0f, amount);
        }

        public static float EaseInOut(float start, float end, float amount) {
            return (float)EaseInOut((double)start, (double)end, amount);
        }

        public static Color EaseInOut(Color start, Color end, float amount) {
            var r = EaseInOut(start.R, end.R, amount);
            var g = EaseInOut(start.G, end.G, amount);
            var b = EaseInOut(start.B, end.B, amount);
            var a = EaseInOut(start.A, end.A, amount);

            return new Color(r,g,b,a);
        }

        public static Vector2 EaseInOut(Vector2 start, Vector2 end, float amount) {
            float x = EaseInOut(start.X, end.X, amount);
            float y = EaseInOut(start.Y, end.Y, amount);

            return new Vector2(x, y);
        }*/

        public static double EaseInOut(double start, double end, float amount) {
            return EaseInOut(start, end, amount, DefaultEaseRate);
        }

        public static float EaseInOut(float start, float end, float amount) {
            return EaseInOut((float)start, (float)end, amount, DefaultEaseRate);
        }

        public static double EaseInOut(double start, double end, float amount, float rate) {
            if (rate < 1) rate = 1;

            double c = end - start;
            double a2;

            if (amount < 0.5)
                a2 = EaseIn(0, 1, amount * 2f, rate) / 2d;
            else
                a2 = 0.5d + EaseOut(0, 1, (amount - 0.5f) * 2f, rate) / 2d;

            return c * a2 + start;
        }

        public static float EaseInOut(float start, float end, float amount, float rate) {
            return (float)EaseInOut((double)start, (double)end, amount, rate);
        }

        public static Color EaseInOut(Color start, Color end, float amount) {
            var r = EaseInOut(start.R, end.R, amount);
            var g = EaseInOut(start.G, end.G, amount);
            var b = EaseInOut(start.B, end.B, amount);
            var a = EaseInOut(start.A, end.A, amount);

            return new Color(r, g, b, a);
        }

        public static Vector2 EaseInOut(Vector2 start, Vector2 end, float amount) {
            float x = EaseInOut(start.X, end.X, amount);
            float y = EaseInOut(start.Y, end.Y, amount);

            return new Vector2(x, y);
        }

        public static Color EaseInOut(Color start, Color end, float amount, float rate) {
            var r = EaseInOut(start.R, end.R, amount, rate);
            var g = EaseInOut(start.G, end.G, amount, rate);
            var b = EaseInOut(start.B, end.B, amount, rate);
            var a = EaseInOut(start.A, end.A, amount, rate);

            return new Color(r, g, b, a);
        }

        public static Vector2 EaseInOut(Vector2 start, Vector2 end, float amount, float rate) {
            float x = EaseInOut(start.X, end.X, amount, rate);
            float y = EaseInOut(start.Y, end.Y, amount, rate);

            return new Vector2(x, y);
        }

        #endregion

        #region ------------------------------ EaseIn ------------------------------
        /* old
        public static double EaseIn(double start, double end, float amount) {
            double diff = end - start;
            double t1 = 0;
            double t2 = diff;

            return Hermite(start, t1, end, t2, amount);
        }

        public static float EaseIn(float start, float end, float amount) {
            return (float)EaseIn((double)start, (double)end, amount);
        }

        public static Color EaseIn(Color start, Color end, float amount) {
            var r = EaseIn(start.R, end.R, amount);
            var g = EaseIn(start.G, end.G, amount);
            var b = EaseIn(start.B, end.B, amount);
            var a = EaseIn(start.A, end.A, amount);

            return new Color(r,g,b,a);
        }

        public static Vector2 EaseIn(Vector2 start, Vector2 end, float amount) {
            float x = EaseIn(start.X, end.X, amount);
            float y = EaseIn(start.Y, end.Y, amount);

            return new Vector2(x, y);
        }
        */

        public static double EaseIn(double start, double end, float amount) {
            return EaseIn(start, end, amount, DefaultEaseRate);
        }

        public static float EaseIn(float start, float end, float amount) {
            return EaseIn((float)start, (float)end, amount, DefaultEaseRate);
        }

        public static double EaseIn(double start, double end, float amount, float rate) {
            if (rate < 1) rate = 1;

            double c = end - start;
            double a2 = System.Math.Pow(amount, rate);

            return c * a2 + start;
        }

        public static float EaseIn(float start, float end, float amount, float rate) {
            return (float)EaseIn((double)start, (double)end, amount, rate);
        }

        public static Color EaseIn(Color start, Color end, float amount) {
            var r = EaseIn(start.R, end.R, amount);
            var g = EaseIn(start.G, end.G, amount);
            var b = EaseIn(start.B, end.B, amount);
            var a = EaseIn(start.A, end.A, amount);

            return new Color(r, g, b, a);
        }

        public static Vector2 EaseIn(Vector2 start, Vector2 end, float amount) {
            float x = EaseIn(start.X, end.X, amount);
            float y = EaseIn(start.Y, end.Y, amount);

            return new Vector2(x, y);
        }

        public static Color EaseIn(Color start, Color end, float amount, float rate) {
            var r = EaseIn(start.R, end.R, amount, rate);
            var g = EaseIn(start.G, end.G, amount, rate);
            var b = EaseIn(start.B, end.B, amount, rate);
            var a = EaseIn(start.A, end.A, amount, rate);

            return new Color(r, g, b, a);
        }

        public static Vector2 EaseIn(Vector2 start, Vector2 end, float amount, float rate) {
            float x = EaseIn(start.X, end.X, amount, rate);
            float y = EaseIn(start.Y, end.Y, amount, rate);

            return new Vector2(x, y);
        }
        #endregion

        #region ------------------------------ EaseOut ------------------------------
        /* old
        public static double EaseOut(double start, double end, float amount) {
            double diff = end - start;
            double t1 = diff;
            double t2 = 0;

            return Hermite(start, t1, end, t2, amount);
        }

        public static float EaseOut(float start, float end, float amount) {
            return (float)EaseOut((double)start, (double)end, amount);
        }

        public static Color EaseOut(Color start, Color end, float amount) {
            var r = EaseOut(start.R, end.R, amount);
            var g = EaseOut(start.G, end.G, amount);
            var b = EaseOut(start.B, end.B, amount);
            var a = EaseOut(start.A, end.A, amount);

            return new Color(r,g,b,a);
        }

        public static Vector2 EaseOut(Vector2 start, Vector2 end, float amount) {
            float x = EaseOut(start.X, end.X, amount);
            float y = EaseOut(start.Y, end.Y, amount);

            return new Vector2(x, y);
        }
        */

        public static double EaseOut(double start, double end, float amount) {
            return EaseOut(start, end, amount, DefaultEaseRate);
        }

        public static float EaseOut(float start, float end, float amount) {
            return EaseOut((float)start, (float)end, amount, DefaultEaseRate);
        }

        public static double EaseOut(double start, double end, float amount, float rate) {
            if (rate < 1) rate = 1;

            double c = end - start;
            double a2 = 1 - EaseIn(0, 1, 1f - amount, rate);

            return c * a2 + start;
        }

        public static float EaseOut(float start, float end, float amount, float rate) {
            return (float)EaseOut((double)start, (double)end, amount, rate);
        }

        public static Color EaseOut(Color start, Color end, float amount) {
            var r = EaseOut(start.R, end.R, amount);
            var g = EaseOut(start.G, end.G, amount);
            var b = EaseOut(start.B, end.B, amount);
            var a = EaseOut(start.A, end.A, amount);

            return new Color(r, g, b, a);
        }

        public static Vector2 EaseOut(Vector2 start, Vector2 end, float amount) {
            float x = EaseOut(start.X, end.X, amount);
            float y = EaseOut(start.Y, end.Y, amount);

            return new Vector2(x, y);
        }

        public static Color EaseOut(Color start, Color end, float amount, float rate) {
            var r = EaseOut(start.R, end.R, amount, rate);
            var g = EaseOut(start.G, end.G, amount, rate);
            var b = EaseOut(start.B, end.B, amount, rate);
            var a = EaseOut(start.A, end.A, amount, rate);

            return new Color(r, g, b, a);
        }

        public static Vector2 EaseOut(Vector2 start, Vector2 end, float amount, float rate) {
            float x = EaseOut(start.X, end.X, amount, rate);
            float y = EaseOut(start.Y, end.Y, amount, rate);

            return new Vector2(x, y);
        }
        #endregion

        // TODO elastic rumgewabbel zu lang
        #region ------------------------------ ElasticIn ------------------------------        
        public static double ElasticIn(double start, double end, float amount) {
            return ElasticIn(start, end, amount, DefaultEaseElasticPeriod);
        }

        public static float ElasticIn(float start, float end, float amount) {
            return ElasticIn((float)start, (float)end, amount, DefaultEaseElasticPeriod);
        }

        public static double ElasticIn(double s, double e, float a, float period) {
            double c = e - s;
            double b = s;
            double a2;


            if (a == 0 || a == 1)
                a2 = a;
            else {
                double s2 = period / 4;
                a = a - 1;
                a2 = (-System.Math.Pow(2, 10 * a) * System.Math.Sin((a - s2) * MathHelper.TwoPi / period));
            }

            return c * a2 + b;
        }

        public static float ElasticIn(float start, float end, float amount, float period) {
            return (float)ElasticIn((double)start, (double)end, amount, period);
        }

        public static Vector2 ElasticIn(Vector2 start, Vector2 end, float amount) {
            float x = ElasticIn(start.X, end.X, amount);
            float y = ElasticIn(start.Y, end.Y, amount);

            return new Vector2(x, y);
        }

        public static Vector2 ElasticIn(Vector2 start, Vector2 end, float amount, float period) {
            float x = ElasticIn(start.X, end.X, amount, period);
            float y = ElasticIn(start.Y, end.Y, amount, period);

            return new Vector2(x, y);
        }

        public static Color ElasticIn(Color start, Color end, float amount) {
            var r = ElasticIn(start.R, end.R, amount);
            var g = ElasticIn(start.G, end.G, amount);
            var b = ElasticIn(start.B, end.B, amount);
            var a = ElasticIn(start.A, end.A, amount);

            return new Color(r, g, b, a);
        }

        public static Color ElasticIn(Color start, Color end, float amount, float period) {
            var r = ElasticIn(start.R, end.R, amount, period);
            var g = ElasticIn(start.G, end.G, amount, period);
            var b = ElasticIn(start.B, end.B, amount, period);
            var a = ElasticIn(start.A, end.A, amount, period);

            return new Color(r, g, b, a);
        }
        #endregion

        #region ------------------------------ ElasticOut ------------------------------
        public static double ElasticOut(double start, double end, float amount) {
            return ElasticOut(start, end, amount, DefaultEaseElasticPeriod);
        }

        public static float ElasticOut(float start, float end, float amount) {
            return ElasticOut((float)start, (float)end, amount, DefaultEaseElasticPeriod);
        }

        public static double ElasticOut(double s, double e, float a, float period) {
            double c = e - s;
            double b = s;
            double a2;


            if (a == 0 || a == 1)
                a2 = a;
            else {
                double s2 = period / 4;
                a2 = (System.Math.Pow(2, -10 * a) * System.Math.Sin((a - s2) * MathHelper.TwoPi / period) + 1);
            }

            return c * a2 + b;
        }

        public static float ElasticOut(float start, float end, float amount, float period) {
            return (float)ElasticOut((double)start, (double)end, amount, period);
        }

        public static Vector2 ElasticOut(Vector2 start, Vector2 end, float amount) {
            float x = ElasticOut(start.X, end.X, amount);
            float y = ElasticOut(start.Y, end.Y, amount);

            return new Vector2(x, y);
        }

        public static Vector2 ElasticOut(Vector2 start, Vector2 end, float amount, float period) {
            float x = ElasticOut(start.X, end.X, amount, period);
            float y = ElasticOut(start.Y, end.Y, amount, period);

            return new Vector2(x, y);
        }

        public static Color ElasticOut(Color start, Color end, float amount) {
            var r = ElasticOut(start.R, end.R, amount);
            var g = ElasticOut(start.G, end.G, amount);
            var b = ElasticOut(start.B, end.B, amount);
            var a = ElasticOut(start.A, end.A, amount);

            return new Color(r, g, b, a);
        }

        public static Color ElasticOut(Color start, Color end, float amount, float period) {
            var r = ElasticOut(start.R, end.R, amount, period);
            var g = ElasticOut(start.G, end.G, amount, period);
            var b = ElasticOut(start.B, end.B, amount, period);
            var a = ElasticOut(start.A, end.A, amount, period);

            return new Color(r, g, b, a);
        }
        #endregion

        #region ------------------------------ ElasticInOut ------------------------------
        public static double ElasticInOut(double start, double end, float amount) {
            return ElasticInOut(start, end, amount, DefaultEaseElasticPeriod);
        }

        public static float ElasticInOut(float start, float end, float amount) {
            return ElasticInOut((float)start, (float)end, amount, DefaultEaseElasticPeriod);
        }

        public static double ElasticInOut(double start, double end, float amount, float period) {
            double c = end - start;
            double b = start;
            double a2;


            if (amount == 0 || amount == 1)
                a2 = amount;
            else {
                amount = amount * 2;
                if (period == 0)
                    period = 0.3f * 1.5f;
                double s = period / 4;

                amount = amount - 1;
                if (amount < 0) {
                    a2 = -0.5f * System.Math.Pow(2, 10 * amount) * System.Math.Sin((amount - s) * MathHelper.TwoPi / period);
                } else {
                    a2 = System.Math.Pow(2, -10 * amount) * System.Math.Sin((amount - s) * MathHelper.TwoPi / period) * 0.5f + 1;
                }
            }

            return c * a2 + b;
        }

        public static float ElasticInOut(float start, float end, float amount, float period) {
            return (float)ElasticInOut((double)start, (double)end, amount, period);
        }

        public static Vector2 ElasticInOut(Vector2 start, Vector2 end, float amount) {
            float x = ElasticInOut(start.X, end.X, amount);
            float y = ElasticInOut(start.Y, end.Y, amount);

            return new Vector2(x, y);
        }

        public static Vector2 ElasticInOut(Vector2 start, Vector2 end, float amount, float period) {
            float x = ElasticInOut(start.X, end.X, amount, period);
            float y = ElasticInOut(start.Y, end.Y, amount, period);

            return new Vector2(x, y);
        }

        public static Color ElasticInOut(Color start, Color end, float amount) {
            var r = ElasticInOut(start.R, end.R, amount);
            var g = ElasticInOut(start.G, end.G, amount);
            var b = ElasticInOut(start.B, end.B, amount);
            var a = ElasticInOut(start.A, end.A, amount);

            return new Color(r, g, b, a);
        }

        public static Color ElasticInOut(Color start, Color end, float amount, float period) {
            var r = ElasticInOut(start.R, end.R, amount, period);
            var g = ElasticInOut(start.G, end.G, amount, period);
            var b = ElasticInOut(start.B, end.B, amount, period);
            var a = ElasticInOut(start.A, end.A, amount, period);

            return new Color(r, g, b, a);
        }
        #endregion

        #region ------------------------------ BounceIn ------------------------------
        public static double BounceIn(double start, double end, float amount) {
            double c = end - start;
            double a2 = 1 - BounceTime(1 - amount);

            return c * a2 + start;
        }

        public static float BounceIn(float start, float end, float amount) {
            return (float)BounceIn((double)start, (double)end, amount);
        }

        public static Color BounceIn(Color start, Color end, float amount) {
            var r = BounceIn(start.R, end.R, amount);
            var g = BounceIn(start.G, end.G, amount);
            var b = BounceIn(start.B, end.B, amount);
            var a = BounceIn(start.A, end.A, amount);

            return new Color(r, g, b, a);
        }

        public static Vector2 BounceIn(Vector2 start, Vector2 end, float amount) {
            float x = BounceIn(start.X, end.X, amount);
            float y = BounceIn(start.Y, end.Y, amount);

            return new Vector2(x, y);
        }
        #endregion

        #region ------------------------------ BounceOut ------------------------------
        public static double BounceOut(double start, double end, float amount) {
            double c = end - start;
            double a2 = BounceTime(amount);

            return c * a2 + start;
        }

        public static float BounceOut(float start, float end, float amount) {
            return (float) BounceOut((double) start, (double) end, amount);
        }

        public static Color BounceOut(Color start, Color end, float amount) {
            var r = BounceOut(start.R, end.R, amount);
            var g = BounceOut(start.G, end.G, amount);
            var b = BounceOut(start.B, end.B, amount);
            var a = BounceOut(start.A, end.A, amount);

            return new Color(r, g, b, a);
        }

        public static Vector2 BounceOut(Vector2 start, Vector2 end, float amount) {
            float x = BounceOut(start.X, end.X, amount);
            float y = BounceOut(start.Y, end.Y, amount);

            return new Vector2(x, y);
        }
        #endregion

        #region ------------------------------ BounceInOut ------------------------------
        public static double BounceInOut(double start, double end, float amount) {
            double c = end - start;
            double a2;

            if (amount < 0.5) {
                amount = amount * 2;
                a2 = (1 - BounceTime(1-amount)) * 0.5f;
            } else
                a2 = BounceTime(amount * 2 - 1) * 0.5f + 0.5f;

            return c * a2 + start;
        }

        public static float BounceInOut(float start, float end, float amount) {
            return (float)BounceInOut((double)start, (double)end, amount);
        }

        public static Color BounceInOut(Color start, Color end, float amount) {
            var r = BounceInOut(start.R, end.R, amount);
            var g = BounceInOut(start.G, end.G, amount);
            var b = BounceInOut(start.B, end.B, amount);
            var a = BounceInOut(start.A, end.A, amount);

            return new Color(r, g, b, a);
        }

        public static Vector2 BounceInOut(Vector2 start, Vector2 end, float amount) {
            float x = BounceInOut(start.X, end.X, amount);
            float y = BounceInOut(start.Y, end.Y, amount);

            return new Vector2(x, y);
        }
        #endregion

        #region EaseBounceHelper
        private static double BounceTime(float amount) {
            double a2;

            if (amount < 1 / 2.75) {
                a2 = 7.5625f * amount * amount;
            } else if (amount < 2 / 2.75) {
                amount -= 1.5f / 2.75f;
                a2 = 7.5625f * amount * amount + 0.75f;
            } else if (amount < 2.5 / 2.75) {
                amount -= 2.25f / 2.75f;
                a2 = 7.5625f * amount * amount + 0.9375f;
            } else {
                amount -= 2.625f / 2.75f;
                a2 = 7.5625f * amount * amount + 0.984375f;
            }

            return a2;
        }
        #endregion

        #region ------------------------------ BackIn ------------------------------
        public static double BackIn(double start, double end, float amount) {            
            double c = end - start;
            double a2 = amount * amount * ((EaseBackOvershoot + 1) * amount - EaseBackOvershoot);

            return c * a2 + start;
        }

        public static float BackIn(float start, float end, float amount) {
            return (float)BackIn((double)start, (double)end, amount);
        }

        public static Color BackIn(Color start, Color end, float amount) {
            var r = BackIn(start.R, end.R, amount);
            var g = BackIn(start.G, end.G, amount);
            var b = BackIn(start.B, end.B, amount);
            var a = BackIn(start.A, end.A, amount);

            return new Color(r, g, b, a);
        }

        public static Vector2 BackIn(Vector2 start, Vector2 end, float amount) {
            float x = BackIn(start.X, end.X, amount);
            float y = BackIn(start.Y, end.Y, amount);

            return new Vector2(x, y);
        }
        #endregion

        #region ------------------------------ BackOut ------------------------------
        public static double BackOut(double start, double end, float amount) {
            double c = end - start;

            amount = amount - 1;
            double a2 = amount * amount * ((EaseBackOvershoot + 1) * amount + EaseBackOvershoot) + 1;

            return c * a2 + start;
        }

        public static float BackOut(float start, float end, float amount) {
            return (float)BackOut((double)start, (double)end, amount);
        }

        public static Color BackOut(Color start, Color end, float amount) {
            var r = BackOut(start.R, end.R, amount);
            var g = BackOut(start.G, end.G, amount);
            var b = BackOut(start.B, end.B, amount);
            var a = BackOut(start.A, end.A, amount);

            return new Color(r, g, b, a);
        }

        public static Vector2 BackOut(Vector2 start, Vector2 end, float amount) {
            float x = BackOut(start.X, end.X, amount);
            float y = BackOut(start.Y, end.Y, amount);

            return new Vector2(x, y);
        }
        #endregion

        #region ------------------------------ BackInOut ------------------------------
        public static double BackInOut(double start, double end, float amount) {
            double c = end - start;
            double a2;
            double overshoot = EaseBackOvershoot * 1.525d;
        	
            amount = amount * 2;
            if (amount < 1) {
                a2 = (amount * amount * ((overshoot + 1) * amount - overshoot)) / 2;
            } else {
                amount = amount - 2;
                a2 = (amount * amount * ((overshoot + 1) * amount + overshoot)) / 2 + 1;
            }

            return c * a2 + start;
        }

        public static float BackInOut(float start, float end, float amount) {
            return (float)BackInOut((double)start, (double)end, amount);
        }

        public static Color BackInOut(Color start, Color end, float amount) {
            var r = BackInOut(start.R, end.R, amount);
            var g = BackInOut(start.G, end.G, amount);
            var b = BackInOut(start.B, end.B, amount);
            var a = BackInOut(start.A, end.A, amount);

            return new Color(r, g, b, a);
        }

        public static Vector2 BackInOut(Vector2 start, Vector2 end, float amount) {
            float x = BackInOut(start.X, end.X, amount);
            float y = BackInOut(start.Y, end.Y, amount);

            return new Vector2(x, y);
        }
        #endregion
    }
}